/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦，本文采用木兰宽松许可证第2版]
 * 
 * https://zhiqim.org/project/zhiqim_kit/zhiqim_git.htm
 *
 * ZhiqimGit is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.git.objer;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

import org.zhiqim.git.GitConstants;
import org.zhiqim.git.objer.objectid.ObjectId;

import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.Streams;

/**
 * 打包索引版本V2，也是GitV1.5.2之后的默认版本，支持大于4G和防损坏<br>
 * 注意：这里输入流去掉了头部8字节<br><br>
 * 
 * [第一部分为索引头部，8字节，前4字节为{-1, 't', 'O', 'c'}，后4字节为版本号]<br>
 * -1tOc<br>
 * 2<br>
 * [第二部分为分段表(FanoutTable)，共256个，fanout[0]-[255]，<br>
 * 格式为从0开始一个对象增加1，最后一个刚好是对象数目，当且仅当fanout[i]-fanout[i-1]>0时有对象<br>
 * 目的是通过oid前字节找到对应的位置，fanout[0]>0x00，fanout[255]>>0xFF]<br>
 * 00000001111111111111111111111111111111111111111111<br>
 * 11111111111111111112222222222222222222222222222222<br>
 * 22222222222222222222222222222222222222222222222222<br>
 * 22222222222222222222222222333333333333333333333333<br>
 * 33333333333333333333333333333333333334444445555666<br>
 * 666666<br>
 * [第三部分为对象数目依次的oid（SHA1码）]<br>
 * 07f3d88e253369fea25376999c673c265d71bc1f<br>
 * 458607711676a976d6728a7160057c6ded4a65dc<br>
 * b0f93e427b9450b0ac536d983cc1feda8cd43172<br>
 * ed4df3c909416ce15dc1a8653600d7ff03c6e99c<br>
 * f39c426bc9e5a6f24768da42ce43a1f225000d89<br>
 * f7fdd4fbde246a8aa92417da9fb39f7bb4edac0a<br>
 * [第四部分为对象数目依次的crc32校验码]<br>
 * 518848863<br>
 * 1216449161<br>
 * -1257651642<br>
 * 105091792<br>
 * 1508939329<br>
 * -1019092888<br>
 * [第五部分为对象数目依次的offset（偏移值）]<br>
 * 218<br>
 * 305<br>
 * 796<br>
 * 293<br>
 * 12<br>
 * 150<br>
 * [第六部分为对象offset64]<br>
 * ...
 * [最后部分为pack和idx文件的SHA1校验码]<br>
 * c97474675db9d619bec0801af2a449ef5d9eda7f<br>
 * ba51e93c43916fa88287440c47f00572fd26f8a0<br><br>
 *
 * @version v1.0.0 @author zhichenggang 2017-4-17 新建与整理
 */
public class PackIndex implements GitConstants
{
    private final int[] fanoutTable = new int[FANOUT];
    private final int count;
    
    //每对象的OID，CRC和offset
    private ObjectId[][] oids = new ObjectId[FANOUT][];
    private long[][] crc32 = new long[FANOUT][];
    private int[][] offset32 = new int[FANOUT][];
    private long[] offset64;

    private byte[] packChecksum;
    
    public PackIndex(InputStream in) throws IOException
    {
        //1.读出分段256个
        byte[] fanoutBuf = Streams.getBytes(in, FANOUT * 4);
        for (int i=0;i<FANOUT;i++)
        {
            fanoutTable[i] = Bytes.BU.getInt(fanoutBuf, i * 4);
        }
        
        //2.对象数目赋值
        count = fanoutTable[FANOUT - 1];

        //2.1 分段读取对象数目，并初始化属性
        for (int i=0;i<FANOUT;i++)
        {
            int num = (i == 0)?fanoutTable[i]:(fanoutTable[i]-fanoutTable[i-1]);
            if (num == 0)
            {//该段没有数据
                oids[i]     = new ObjectId[0];
                crc32[i]    = _EMPTY_L_;
                offset32[i] = _EMPTY_I_;
            }
            else
            {//有数据初始化数组
                int intNum = (int)num;
                oids[i] = new ObjectId[intNum];
                crc32[i] = new long[intNum];
                offset32[i] = new int[intNum];
            }
        }
        
        //3.分段读取对象编号
        for (int i=0;i<FANOUT;i++)
        {
            int num = oids[i].length;
            if (num == 0)
                continue;
            
            byte[] buf = Streams.getBytes(in, num * LEN_OBJECT_ID);
            for (int j=0;j<num;j++)
            {//读取到数组
                oids[i][j] = ObjectId.parse(buf, j * LEN_OBJECT_ID);
            }
        }
        

        //4.分段读取CRC32值
        for (int i=0;i<FANOUT;i++)
        {
            int num = crc32[i].length;
            if (num == 0)
                continue;
            
            byte[] buf = Streams.getBytes(in, num * 4);
            for (int j=0;j<num;j++)
            {//读取到数组
                crc32[i][j] = Bytes.BU.getIntUnsigned(buf, j * 4);
            }
        }

        //5.分段读取offset32值，并判断是否超过32位
        int o64cnt = 0;
        for (int i=0;i<FANOUT;i++)
        {
            int num = offset32[i].length;
            if (num == 0)
                continue;
            
            for (int j=0;j<num;j++)
            {
                offset32[i][j] = Bytes.BU.getInt(Streams.getBytes(in, 4), 0);
                if (offset32[i][j] < 0){
                    o64cnt++;
                }
            }
        }

        //6.读取大对象64位的值
        if (o64cnt == 0)
            offset64 = _EMPTY_L_;
        else
        {
            offset64 = new long[o64cnt];
            for (int i=0;i<o64cnt;i++)
            {
                offset64[i] = Bytes.BU.getLong(Streams.getBytes(in, 8), 0);
            }
        }

        //7.读取检验码
        packChecksum = Streams.getBytes(in, LEN_OBJECT_ID);
    }
    
    /***********************************************************************************************/
    //获取属性
    /***********************************************************************************************/

    public byte[] getPackCheckSum()
    {
        return packChecksum;
    }
    
    /** 获取对象数目 */
    public int count()
    {
        return count;
    }
    
    /** 大文件对象数目 */
    public int count64()
    {
        return offset64.length;
    }
    
    /** 判断是否有对象 */
    public boolean hasObject(ObjectId oid)
    {
        int i = oid.getFirstByte();
        return Arrays.binarySearch(oids[i], oid) >= 0;
    }
    
    /** 获取对象对应的CRC值 */
    public long findCRC32(ObjectId oid)
    {
        int i = oid.getFirstByte();
        int j = Arrays.binarySearch(oids[i], oid);
        if (j == -1)
            throw Asserts.exception(oid.toString() + "未知类型");
        
        return crc32[i][j];
    }
    
    /** 获取对象对应的偏移值 */
    public long findOffset(ObjectId oid)
    {
        int i = oid.getFirstByte();
        int j = Arrays.binarySearch(oids[i], oid);
        if (j < 0)
            return -1;
        
        int p = offset32[i][j];
        return (p >= 0)?p:offset64[(int)(p & ~IS_O64)];
    }
}
